#include <iostream>
#include <string>
#include <vector>
#include <boost/filesystem.hpp> 
#include "util.hpp"

const std::string src_path = "data/input"; // 下面放的是所有的 html 网页
const std::string output = "data/cln_html/cln.txt"; // 放清洗过的数据

typedef struct DocInfo 
{
	std::string title; // 文档标题
	std::string content; // 文档内容
	std::string url; // 文档的 url
}DocInfo_t;

bool EnumFile(const std::string& src_path, std::vector<std::string>* files_list);
bool ParseHtml(const std::vector<std::string>& files_list, std::vector<DocInfo_t>* results);
bool SaveHtml(const std::vector<DocInfo_t>& results, const std::string& output);

int main()
{
	std::vector<std::string> files_list;
	// 第一步：递归地把每个 html 文件名带路径保存到 files_list 中, 方便后期对文件进行读取
	if (!EnumFile(src_path, &files_list))
	{
		std::cerr << "enum file name error!" << std::endl;
		return 1;
	}

	// 第二步：按照 files_list 读取每个文件的内容，并进行解析，解析为 DocInfo 格式
	std::vector<DocInfo_t> results;
	if (!ParseHtml(files_list, &results))
	{
		std::cerr << "parse html error!" << std::endl;
		return 2;
	}

	// 第三步：把解析完毕的各个文件的内容(results)，写入到 output -- cln.txt 中
	// 按照 \3 作为每个文档的分隔符
	if (!SaveHtml(results, output))
	{
		std::cerr << "save html error" << std::endl;
		return 3;
	}

	return 0;
}

bool EnumFile(const std::string& src_path, std::vector<std::string>* files_list)
{
	boost::filesystem::path root_path(src_path);

	// 判断文件是否存在 
	if (!boost::filesystem::exists(root_path))
	{
		std::cerr << src_path << " not exists" << std::endl;
		return false;
	}

	// 定义一个空的迭代器，用来判断递归结束
	boost::filesystem::recursive_directory_iterator end;

	for (boost::filesystem::recursive_directory_iterator iter(root_path); iter != end; iter++)
	{
		// 文件名筛选：判断是否是 regular file -- 常规文件
		if (!boost::filesystem::is_regular_file(*iter))
		{
			continue; // 不 push 到 files_list 中，continue 忽略
		}

		// 判断文件路径名的后缀是否为 .html
		// path() 获取一个路径对象
		if (iter->path().extension() !=".html")
		{
			continue;
		}
		//std::cout << "debug: " << iter->path().string() << std::endl;

		// 当前的路径一定是合法的，以 html 结尾的普通网页文件
		// 把迭代器对应路径转成 string 放入 vector 中
		files_list->push_back(iter->path().string());
	}

	return true;
}

static bool ParseTitle(const std::string& file, std::string* title)
{
	std::size_t begin = file.find("<title>");
	if (begin == std::string::npos)
	{
		return false;
	}

	std::size_t end = file.find("</title>");
	if (end == std::string::npos)
	{
		return false;
	}

	begin += std::string("<title>").size();
	
	// 如果 begin > end 说明没有标题，不能截取，虽然 begin == end 也是没有标题，但是下面截取不会出现问题，就当让标题为空，
	// 此时和原本默认构造的 title 内容是一样的，也算它对
	if (begin > end)
	{
		return false;
	}
	*title = file.substr(begin, end - begin);
	return true;
}

static bool ParseContent(const std::string& file, std::string* content)
{
	// 基于一个简易状态机编写
	enum status
	{
		LABLE,  // 在读 标签
		CONTENT // 在读 content 内容
	};

	enum status s = LABLE;
	for (char c: file)
	{
		switch(s)
		{
			case LABLE:
				// 处于 LABLE 状态时，不需要处理内容，只要判断是否改变状态即可
				if (c == '>') s = CONTENT; 
				break;
			case CONTENT:
				if (c == '<') s = LABLE; // 如果读到 < 说明把 content 内容读完了
				else // 处理内容
				{
					// 不保留原始文件中的 '\n'，因为我们想用 '\n' 作为 html 解析之后的文本的分隔符
					if (c == '\n') c = ' ';
					content->push_back(c);
				}
				break;
			default:
				break;
		}
	}

	return true;
}

static bool ParseUrl(const std::string& file_path, std::string* url)
{
	std::string url_head = "https://www.boost.org/doc/libs/1_85_0/doc/html";
	std::string url_tail = file_path.substr(src_path.size()); // 从 src_path 处截取，截取的就是 /...html 

	*url = url_head + url_tail;
	return true;
}

static void ShowDoc(const DocInfo_t& doc)
{
	std::cout << "title: " << doc.title << std::endl;
	std::cout << "content: " << doc.content << std::endl;
	std::cout << "url: " << doc.url << std::endl;
}

bool ParseHtml(const std::vector<std::string>& files_list, std::vector<DocInfo_t>* results)
{
	for (const std::string& file : files_list)
	{
		std::string result;
		// 1. 读取文件 Readfile() 失败直接 continue
		if (!ns_util::FileUtil::ReadFile(file, &result)) 
		{
			continue;
		}
		DocInfo_t doc;
		// 2. 解析文件，提取 title
		// 把解析的内容放到 doc 的 title 中，解析失败， continue
		if (!ParseTitle(result, &doc.title))
		{
			continue;
		}
		// 3. 解析文件，提取 content
		// 提取文档内容，本质就是去标签，把标签去掉后，内容放到 doc 的 content 中
		if (!ParseContent(result, &doc.content))
		{
			continue;
		}
		// 4. 解析文件路径，构建 url
		// file 就是文件路径
		if (!ParseUrl(file, &doc.url))
		{
			continue;
		}

		// 到这里完成了解析任务，当前文档的相关内容都放到了 doc 中
		results->push_back(std::move(doc)); // bug: 还处理，push_back 本质是拷贝，效率可能比较低

		// for debug
		//ShowDoc(doc);
		//break;
	}

	return true;
}

bool SaveHtml(const std::vector<DocInfo_t>& results, const std::string& output)
{
#define SEP '\3'
	// 以二进制方式写入
	std::ofstream out(output, std::ios::out | std::ios::binary);
	if (!out.is_open())
	{
		std::cerr << "open: " << output << " failed!" << std::endl;
		return false;
	}

	// 进行文件内容的写入
	for (auto &item: results)
	{
		std::string out_string;
		out_string += item.title;
		out_string += SEP;
		out_string += item.content;
		out_string += SEP;
		out_string += item.url;
		out_string += '\n';

		out.write(out_string.c_str(), out_string.size());
	}

	out.close();

	return true;
}